// 我们可以通过交叉类型的方式来实现信息的附加：
export declare class TagProtector<T extends string> {
	protected __tag__: T;
}
export type Nominal<T, U extends string> = T & TagProtector<U>;
// 在这里我们使用 TagProtector 声明了一个具有 protected 属性的类，使用它来携带额外的信息，并和原本的类型合并到一起，就得到了 Nominal 工具类型。
export type CNY1 = Nominal<number, 'CNY1'>;
export type USD1 = Nominal<number, 'USD1'>;

declare const tag: unique symbol;
declare type Tagged<Token> = {
	readonly [tag]: Token;
};
export type Opaque<Type, Token = unknown> = Type & Tagged<Token>;
(function () {
	type USD = number;
	type CNY = number;

	const CNYCount: CNY = 200;
	const USDCount: USD = 200;

	function addCNY(source: CNY, input: CNY) {
		return source + input;
	}
	//在结构化类型系统中，USD 与 CNY （分别代表美元单位与人民币单位）被认为是两个完全一致的类型，因此在 addCNY 函数中可以传入 USD 类型的变量。这就很离谱了，人民币与美元这两个单位实际的意义并不一致，怎么能进行相加？
	//在标称类型系统中，CNY 与 USD 被认为是两个完全不同的类型，因此能够避免这一情况发生。
	console.log(addCNY(CNYCount, USDCount));

	// # 在 TypeScript 中模拟标称类型系统
	//类型的重要意义之一是限制了数据的可用操作与实际意义。这往往是通过类型附带的额外信息来实现的（类似于元数据），要在 TypeScript 中实现，其实我们也只需要为类型额外附加元数据即可，比如 CNY 与 USD，我们分别附加上它们的单位信息即可，但同时又需要保留原本的信息（即原本的 number 类型）。

	//有了 Nominal 这个工具类型，我们可以尝试来改进下上面的例子了：
	const CNYCount1 = 100 as CNY1;
	const USDCount1 = 100 as USD1;
	function addCNY1(source: CNY1, input: CNY1) {
		return (source + input) as CNY1;
	}

	const res: CNY1 = addCNY1(CNYCount1, CNYCount1);
	console.log('res__', res);

	// addCNY1(CNYCount1, USDCount1); // 报错了！

	//我们还可以从逻辑层面入手进一步确保安全性：
	class CNY2 {
		private __tag!: void;
		constructor(public value: number) {}
	}
	class USD2 {
		private __tag!: void;
		constructor(public value: number) {}
	}
	//---相应的，现在使用方式也要进行变化：
	const CNYCount2 = new CNY2(100);

	const USDCount2 = new USD2(110);
	console.log(CNYCount2.value, USDCount2.value, '\n' + JSON.stringify(CNYCount2));
	function addCNY2(source: CNY2, input: CNY2) {
		return source.value + input.value;
	}
	addCNY2(CNYCount2, CNYCount2);

	// addCNY2(CNYCount2, USDCount2);// 报错了！
})();
